/*
 * Copyright 2007-2008 Andrew O'Malley
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */


package ws.quokka.plugin.junit;

import org.apache.tools.ant.taskdefs.optional.junit.BatchTest;
import org.apache.tools.ant.taskdefs.optional.junit.FormatterElement;
import org.apache.tools.ant.taskdefs.optional.junit.JUnitTask;
import org.apache.tools.ant.types.Environment;
import org.apache.tools.ant.types.Path;
import org.apache.tools.ant.types.ResourceCollection;

import ws.quokka.core.plugin_spi.support.Keys;
import ws.quokka.core.plugin_spi.support.Setter;
import ws.quokka.core.plugin_spi.support.TypedProperties;

import ws.quokka.plugin.lifecycle.LifeCycle;
import ws.quokka.plugin.lifecycle.LifeCycleImpl;

import java.io.File;

import java.util.Collection;
import java.util.Iterator;
import java.util.Locale;
import java.util.Map;


/**
 *
 */
public class JunitPlugin extends LifeCycleImpl {
    //~ Static fields/initializers -------------------------------------------------------------------------------------

    private static final String TEMP_DIR = "tempDir";
    private static final String[] ATTRIBUTES = new String[] {
            "printSummary", "fork", "forkMode", "haltOnError", "errorProperty", "haltOnFailure", "failureProperty",
            "filterTrace", "timeout", "maxMemory", "jvm", "dir", "newEnvironment", "includeAntRuntime", "showOutput",
            "outputToFormatters", TEMP_DIR, "reloading", "cloneVm"
        };
    private static final String SYSPROPERTY = "sysproperty";
    private static final String BATCH_TEST = "batchTest";
    private static final String[] BATCH_TEST_ATTRIBUTES = new String[] {
            "fork", "haltOnError", "errorProperty", "haltOnFailure", "failureProperty", "filterTrace", "toDir", "if",
            "unless"
        };
    private static final String RC = "rc";
    private static final String FORMATTER = "formatter.";
    private static final String JVM_ARGS = "jvmArgs";
    private static final String[] FORMATTER_ATTRIBUTES = new String[] {
            "type", "className", "extension", "useFile", "if", "unless"
        };

    //~ Methods --------------------------------------------------------------------------------------------------------

    public void test() throws Exception {
        doTest();
    }

    public void integrationTest() throws Exception {
        doTest();
    }

    private void doTest() throws Exception {
        JUnitTask jUnit = (JUnitTask)utils().init(new JUnitTask(), "junit");

        // Set attributes
        Setter setter = new Setter(properties());
        utils().mkdir(properties().getFile(TEMP_DIR));
        setter.set(jUnit, ATTRIBUTES);
        properties().verify(new Keys(ATTRIBUTES), new Keys(SYSPROPERTY + "[").add(BATCH_TEST + "[").add(FORMATTER).add(JVM_ARGS));

        // Set up classpath
        Path classpath = new Path(getProject());

        if (isTargetEnabled(LifeCycle.INSTRUMENT_COMPILED)) {
            classpath.add(new Path(getProject(), getLifeCycle().getInstrumentCompiledOutput().getPath()));
            classpath.add(getLifeCycle().getState().getInstrumentedTestPath());

            // Expose as system properties to allow custom class loaders a way to obtain paths
            jUnit.addConfiguredSysproperty(toVariable("quokka.junit.instrumentCompiledOutput",
                    getLifeCycle().getInstrumentCompiledOutput().getPath()));
            jUnit.addConfiguredSysproperty(toVariable("quokka.junit.instrumentTestPath",
                    getLifeCycle().getState().getInstrumentedTestPath().toString()));
        }

        classpath.add(new Path(getProject(), getLifeCycle().getComplieOutput().getPath()));
        classpath.add(new Path(getProject(), getLifeCycle().getTestComplieOutput().getPath()));
        classpath.add(getResources().getPathGroupAsPath("test"));

        File toolsJar = getToolsJar();

        if (toolsJar != null) { // TODO make configurable as to whether tools.jar is added
            log().verbose("Adding tools.jar to jUnit classpath from " + toolsJar.getPath());
            classpath.add(new Path(getProject(), toolsJar.getPath()));
        }

        jUnit.createClasspath().add(classpath);

        for (Iterator i = properties().getMap(SYSPROPERTY, false, String.class).entrySet().iterator(); i.hasNext();) {
            Map.Entry entry = (Map.Entry)i.next();

            if (log().isDebugEnabled()) {
                log().debug(SYSPROPERTY + ": " + entry);
            }

            jUnit.addConfiguredSysproperty(toVariable((String)entry.getKey(), (String)entry.getValue()));
        }

        // TODO: Support tests
        // Create a batch of tests
        Collection batchTests = properties().getMap(BATCH_TEST, true, null).values();
        boolean added = false;

        for (Iterator i = batchTests.iterator(); i.hasNext();) {
            TypedProperties props = (TypedProperties)i.next();
            BatchTest batchTest = jUnit.createBatchTest();
            Setter subSet = new Setter(props);
            subSet.set(batchTest, BATCH_TEST_ATTRIBUTES);
            props.verify(new Keys(BATCH_TEST_ATTRIBUTES), new Keys(RC + "."));

            // TODO: Support nested formatters
            if (batchTest.getTodir() != null) {
                utils().mkdir(new File(batchTest.getTodir())); // Must exist or it barfs
            }

            ResourceCollection rc = props.getResourceCollection(RC);

            if (rc.size() != 0) {
                added = true;
                batchTest.add(rc);
            }
        }

        if (!added) {
            // Unfortunately, the jUnit task does class path processing before checking if there
            // are any tests to perform. It therefore falls over if jUnit is not on the class path
            // In our case, users will probably on add jUnit to a path when there are tests
            // => check up front and bypass
            log().verbose("Skipping testing as no tests have been found");

            return;
        }

        // Set up the formatter
        FormatterElement fe = new FormatterElement();
        TypedProperties formatterProps = properties().sub(FORMATTER);
        formatterProps.verify(new Keys(FORMATTER_ATTRIBUTES));

        Setter subSet = new Setter(formatterProps);
        subSet.set(fe, FORMATTER_ATTRIBUTES);
        jUnit.addFormatter(fe);

        jUnit.createJvmarg().setLine(properties().getString(JVM_ARGS, null));

        jUnit.perform();
    }

    private Environment.Variable toVariable(String key, String value) {
        Environment.Variable variable = new Environment.Variable();
        variable.setKey(key);
        variable.setValue(value);

        return variable;
    }

    public File getToolsJar() {
        // Fragment taken from org.apache.tools.ant.launch.Locator
        // based on java.home setting
        String javaHome = System.getProperty("java.home");
        File toolsJar = new File(javaHome + "/lib/tools.jar");

        if (toolsJar.exists()) {
            // Found in java.home as given
            return toolsJar;
        }

        if (javaHome.toLowerCase(Locale.US).endsWith(File.separator + "jre")) {
            javaHome = javaHome.substring(0, javaHome.length() - 4);
            toolsJar = new File(javaHome + "/lib/tools.jar");
        }

        if (!toolsJar.exists()) {
            // Doesn't exist in os x JVMs, so ignore if not found
            log().verbose("Unable to locate tools.jar. " + "Expected to find it in " + toolsJar.getPath());

            return null;
        }

        return toolsJar;
    }
}
